package bond

import (
	"fmt"
	"io/fs"
	"path/filepath"
	"reflect"

	"gitee.com/wmdng/vake/ninja"
	"gitee.com/wmdng/vake/parser"
)

/*

[global variable]

rulefile : string

[struct]

Program,
	target : string
	source : Node, []Node

	libpath: string, []string,
	libs : string, []string

Archive:
	target : string
	source : Node, []Node

Object:
	cpppath: string, []string
	objpath: string
	source : Node, []Node

File:
	type: string : "c", "arm", "object", "program", "library"
	srcpath : string
	pattern : string, []string : golang/path/filepath/Glob??
	exclude : string : pattern

*/

type Context struct {
	opt *Option
}

func (ctx Context) GetVariable(ident string) (interface{}, error) {
	return ctx.opt.GetField(ident)
}

func (ctx Context) AddVariable(ident string, val interface{}) {
	ctx.opt.AddField(ident, val)
}

/*
形式1 : 最简单的形式, srcpath, pattern
	srcpath 只能是一个字符串, 表示源文件所在目录
	pattern 如果一个字符串,这样仅仅表示一个文件.
         可以是一个匹配表达式? 表示过滤多个文件, 比如 *.c
	     也可以是字符串串数组, 表示多个文件.
		 字符串数组可以混杂表示式数组?
*/
func (ctx Context) fileFilter(opt *Option) (*NFile, error) {
	var dir, ext string
	var rult []string
	var err error

	/**/
	dir, err = opt.ExpectString("srcpath", true)
	if err != nil {
		return nil, err
	}

	ext, err = opt.ExpectString("type", true)
	if err != nil {
		return nil, err
	}

	/**/
	ptns, err := opt.ExpectStrArray("pattern", true)
	if err != nil {
		return nil, err
	}

	if len(ptns) <= 0 {
		return nil, fmt.Errorf("pattern is null")
	}

	filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}

		for _, vv := range ptns {
			mm, err := filepath.Match(vv, info.Name())
			if err != nil {
				return err
			}

			if mm {
				rult = append(rult, path)
			}
		}

		/**/
		return nil
	})

	/**/
	if len(rult) <= 0 {
		return nil, fmt.Errorf("not found any source file")
	}

	/**/
	return &NFile{ext, rult, opt}, nil
}

/*
objpath : 字符串, 指定输出目录, 所有输出的 obj 都会放到这里.
 source : 变量引用, 当前只能是指向一个 File 结构.
*/
func (ctx Context) consObject(opt *Option) (*NObject, error) {
	var dir string
	var err error

	dir, err = opt.ExpectString("objpath", true)
	if err != nil {
		return nil, err
	}

	/**/
	nv, err := opt.ExpectNodeArray("source", true)
	if err != nil {
		return nil, err
	}

	/**/
	return &NObject{dir, nv, opt}, nil
}

func (ctx Context) consArchive(opt *Option) (*NArchive, error) {
	var dst string
	var err error

	dst, err = opt.ExpectString("target", true)
	if err != nil {
		return nil, err
	}

	/**/
	nv, err := opt.ExpectNodeArray("source", true)
	if err != nil {
		return nil, err
	}

	/**/
	return &NArchive{dst, nv, opt}, nil
}

/*
target : 字符串, 指定输出目录, 所有输出的 obj 都会放到这里.
source : 变量引用, 当前只能是指向一个 Object 结构.
*/
func (ctx Context) consProgram(opt *Option) (*NProgram, error) {

	var dst string
	var err error

	dst, err = opt.ExpectString("target", true)
	if err != nil {
		return nil, err
	}

	/**/
	nv, err := opt.ExpectNodeArray("source", true)
	if err != nil {
		return nil, err
	}

	return &NProgram{dst, nv, opt}, nil
}

/*
target : 字符串, 输出文件名字.
source : 结构对象, 期望是 program .
*/
func (ctx Context) consBinary(opt *Option) (*NBianry, error) {

	var dst string
	var err error

	dst, err = opt.ExpectString("target", true)
	if err != nil {
		return nil, err
	}

	/**/
	nv, err := opt.ExpectNodeArray("source", true)
	if err != nil {
		return nil, err
	}

	return &NBianry{dst, nv, opt}, nil
}

func (ctx Context) convArray(pay *parser.SArray) (interface{}, error) {

	var sary []interface{}
	var ok bool

	if len(pay.Vals) <= 0 {
		return nil, nil
	}

	for _, vv := range pay.Vals {

		switch nval := vv.(type) {

		case *parser.SLabel:
			nrv, err := ctx.GetVariable(nval.Rfnm)
			if err != nil {
				return nil, err
			} else {
				sary = append(sary, nrv)
			}

		case *string:
			sary = append(sary, *nval)

		default:
			return nil, fmt.Errorf("array of string or node is allow, other todo-1.")
		}
	}

	/* second */
	_, ok = sary[0].(string)
	if ok {
		var asss []string

		for _, vv := range sary {

			ts, ok := vv.(string)
			if !ok {
				return nil, fmt.Errorf("array of string or node is allow, other todo-3.")
			}

			asss = append(asss, ts)

		}

		return asss, nil
	}

	_, ok = sary[0].(Node)
	if ok {
		var ands []Node

		for _, vv := range sary {

			tn, ok := vv.(Node)
			if !ok {
				return nil, fmt.Errorf("array of string or node is allow, other todo-3.")
			}

			ands = append(ands, tn)

		}

		/**/
		return ands, nil
	}

	/* other type not support, todo??? */
	return nil, fmt.Errorf("array of string or node is allow, other todo-2.")
}

func (ctx Context) convStruct(pst *parser.SStruct) (Node, error) {

	opt := NewOption()

	for k, v := range pst.Fields {

		switch val := v.(type) {
		case *int64:
			opt.AddField(k, *val)

		case *float64:
			opt.AddField(k, *val)

		case *string:
			opt.AddField(k, *val)

		case *parser.SLabel:
			vr, err := ctx.GetVariable(val.Rfnm)
			if err != nil {
				return nil, err
			} else {
				opt.AddField(k, vr)
			}

		case *parser.SArray:
			vx, err := ctx.convArray(val)
			if err != nil {
				return nil, err
			} else {
				opt.AddField(k, vx)
			}

		default:
			return nil, fmt.Errorf("")
		}
	}

	switch pst.Sname {
	case "File":
		/* filter from directory */
		return ctx.fileFilter(opt)

	case "Object":
		/**/
		return ctx.consObject(opt)

	case "Archive":
		/**/
		return ctx.consArchive(opt)

	case "Program":
		return ctx.consProgram(opt)

	case "Binary":
		return ctx.consBinary(opt)

	default:
		return nil, fmt.Errorf("struct %s not support", pst.Sname)
	}
}

func (ctx Context) ConvGlobal(expres []*parser.SExpre) error {

	for _, exp := range expres {
		// fmt.Printf("t1: %s, %v\n", exp.Ident, reflect.TypeOf(exp.Vval))

		switch vv := exp.Vval.(type) {
		case *string:
			ctx.AddVariable(exp.Ident, *vv)

		case *int64:
			ctx.AddVariable(exp.Ident, *vv)

		case *float64:
			ctx.AddVariable(exp.Ident, *vv)

		case *parser.SLabel:
			vr, err := ctx.GetVariable(vv.Rfnm)
			if err != nil {
				return fmt.Errorf("%v:%v", exp.Pos, err)
			} else {
				ctx.AddVariable(exp.Ident, vr)
			}

		case *parser.SArray:
			av, err := ctx.convArray(vv)
			if err != nil {
				return fmt.Errorf("%v:%v", exp.Pos, err)
			} else {
				ctx.AddVariable(exp.Ident, av)
			}

		case *parser.SStruct:
			sv, err := ctx.convStruct(vv)
			if err != nil {
				return fmt.Errorf("%v:%v", exp.Pos, err)
			} else {
				ctx.AddVariable(exp.Ident, sv)
			}

		default:
			return fmt.Errorf("%v:value type not support(%v)", exp.Pos, reflect.TypeOf(vv))
		}
	}

	return nil
}

func (ctx Context) procConfig(nj *ninja.Ninja) error {

	rc, err := ctx.GetVariable("rulefile")
	if err != nil {
		return fmt.Errorf("rulefile variable must define")
	}

	rcn, ok := rc.(string)
	if !ok {
		return fmt.Errorf("rulefile variable must string")
	}

	// try open
	nj.SetTemplate(rcn)
	return nil
}

/*

const cortexarg = "-target-abi aapcs -target-feature +strict-align -target-feature +soft-float -target-feature +soft-float-abi -msoft-float -mfloat-abi soft"
const rv32arg = "-target-abi ilp32"

func (ctx Context) procConfig(nj *ninja.Ninja) error {
	var rcn string
	var rc interface{}

	// cpu
	rc, err := ctx.GetVariable("cpu")
	if err != nil {
		rc = interface{}("x86-64")
	}

	rcn, ok := rc.(string)
	if !ok {
		return fmt.Errorf("cpu variable must string")
	}

	switch rcn {
	case "cortex-m3":
		cccpu := ninja.NewVariable("cccpu", "-triple thumbv7m-none-eabi -target-cpu cortex-m3 "+cortexarg)
		nj.AddVariable(cccpu)

		ascpu := ninja.NewVariable("ascpu", "-triple thumbv7m-none-eabi -target-cpu cortex-m3 +thumb-mode -mllvm --arm-add-build-attributes")
		nj.AddVariable(ascpu)

		ldcpu := ninja.NewVariable("ldcpu", "-m armelf")
		nj.AddVariable(ldcpu)

	case "cortex-m0":
		cccpu := ninja.NewVariable("cccpu", "-triple thumbv6m-none-eabi -target-cpu cortex-m0plus "+cortexarg)
		nj.AddVariable(cccpu)

		ascpu := ninja.NewVariable("ascpu", "-triple thumbv6m-none-eabi -target-cpu cortex-m0plus -target-feature +thumb-mode -mllvm --arm-add-build-attributes")
		nj.AddVariable(ascpu)

		ldcpu := ninja.NewVariable("ldcpu", "-m armelf")
		nj.AddVariable(ldcpu)

	case "rv32imc":
		cccpu := ninja.NewVariable("cccpu", "-triple riscv32-none-eabi -target-cpu generic-rv32 -target-feature +m -target-feature +c "+rv32arg)
		nj.AddVariable(cccpu)

		ascpu := ninja.NewVariable("ascpu", "-triple riscv32-none-eabi -target-cpu generic-rv32 -target-feature +m -target-feature +c")
		nj.AddVariable(ascpu)

		ldcpu := ninja.NewVariable("ldcpu", "-m elf32lriscv")
		nj.AddVariable(ldcpu)

	case "rv32im":
		cccpu := ninja.NewVariable("cccpu", "-triple riscv32-none-eabi -target-cpu generic-rv32 -target-feature +m "+rv32arg)
		nj.AddVariable(cccpu)

		ascpu := ninja.NewVariable("ascpu", "-triple riscv32-none-eabi -target-cpu generic-rv32 -target-feature +m ")
		nj.AddVariable(ascpu)

		ldcpu := ninja.NewVariable("ldcpu", "-m elf32lriscv")
		nj.AddVariable(ldcpu)

	case "x86-64":
		cccpu := ninja.NewVariable("cccpu", "")
		nj.AddVariable(cccpu)

		ascpu := ninja.NewVariable("ascpu", "")
		nj.AddVariable(ascpu)

		ldcpu := ninja.NewVariable("ldcpu", "")
		nj.AddVariable(ldcpu)

	default:
		return fmt.Errorf("cpu variable not support, %q", rcn)
	}

	//
	rt, err := ctx.GetVariable("sysroot")
	if err != nil {
		return fmt.Errorf("need sysroot variable, %s", err.Error())
	}

	//
	rtn, ok := rt.(string)
	if !ok {
		return fmt.Errorf("sysroot variable must string")
	}

	//
	sroot := ninja.NewVariable("ccopt", "-std=c99 -disable-free -disable-llvm-verifier -mrelocation-model static -mconstructor-aliases "+
		"-fno-caret-diagnostics -fno-diagnostics-fixit-info -fcolor-diagnostics "+
		"-Oz -nostdsysteminc -isystem "+filepath.Join(rtn, "/inc"))
	nj.AddVariable(sroot)

	//
	ldroot := ninja.NewVariable("ldopt", "--library-path="+filepath.Join(rtn, "/lib"))
	nj.AddVariable(ldroot)

	return nil
}

*/

func Convert(expres []*parser.SExpre, nj *ninja.Ninja) error {

	var ctx Context

	/**/
	ctx.opt = NewOption()
	err := ctx.ConvGlobal(expres)
	if err != nil {
		return fmt.Errorf("error, %s", err.Error())
	}

	/**/
	rt, err := ctx.GetVariable("default")
	if err != nil {
		return fmt.Errorf("need default variable, %s", err.Error())
	}

	/**/
	rtn, ok := rt.(Node)
	if !ok {
		return fmt.Errorf("default variable must a Node struct")
	}

	/**/
	dst := rtn.GetExport()
	nj.SetDefault(dst[0])

	/* rule define template */
	err = ctx.procConfig(nj)
	if err != nil {
		return fmt.Errorf("proc config, %v", err)
	}

	return rtn.RunGenerate(nj)
}
